package org.zhongyin.Forecast;

import java.io.IOException;
import java.sql.Timestamp;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Map;

import jxl.read.biff.BiffException;

import org.joda.time.DateTime;
import org.zhongyin.ARXModel.ARXFirstWDProvider;
import org.zhongyin.ARXModel.ARXFirstWEProvider;
import org.zhongyin.ARXModel.ARXManager;
import org.zhongyin.ARXModel.ARXSecondWDProvider;
import org.zhongyin.ARXModel.ARXSecondWEProvider;
import org.zhongyin.ARXModel.IARXModel;
import org.zhongyin.Dao.LocalDBDaoImpl;
import org.zhongyin.Regression.Regression;
import org.zhongyin.Util.Utility;

import Jama.Matrix;

/**
 * 
 * @author ZhongYin Zhang
 *
 */
public class Forecasting {
	
	private ARXManager arxManager = new ARXManager();
	
	
	public Forecasting() throws IOException, ParseException, BiffException {
		
	}
	
	
	/**
	 * calculate the cofficient based on IARXModel
	 * @param test data structure of IARXModel
	 * @return cofficient in Matrix
	 */
	private Matrix calculateCofficient(IARXModel test, int type){
		
		double[][] array_X = test.getARXmodel_X();

		double[] array_Y = test.getARXmodel_Y();
		
		int length = array_Y.length;

		Matrix weight = Matrix.identity(length, length);

		Regression regression = new Regression();

		weight = regression.getIterWeight(array_Y, array_X, weight, type);

		
		if(Utility.DEBUG){
			System.out.println("Forecasting.java calculateCofficient()");
			Utility.printArraySingleLine(test.getARXmodel_X());
			System.out.println(Arrays.toString(test.getARXmodel_Y()));
			 Utility.printArraySingleLine(weight.getArray());
		}
		
		

		Matrix cofficient = regression.getWeightedCoefficient(array_Y,
				array_X, weight);
		return cofficient;
		
	}

	/**
	 * Get next 6 hours data based on current hour
	 * Use ARX(1,2,24) for next hour, ARX(2,3,24) for the next second hour,
	 * ARX(3,4,24) for the next third hour, ARX(4,5,24) for the next fourth hour,
	 * ARX(5,6,24) for the next fifth hour, ARX(6,7,24) for the next sixth hour.
	 * @return next 6 hours data based on current hour
	 * @throws BiffException
	 * @throws ParseException
	 * @throws IOException
	 */
	public List<double[]> getNext6HoursData() throws BiffException, ParseException,
			IOException {
		DateTime now = new DateTime();
		
		IARXModel forecastWE = arxManager.createFirstWE();
		
		forecastWE.setForecastData();

		IARXModel forecastWD = arxManager.createFirstWD();
		forecastWD.setForecastData();
		
		List<double[]> result = new ArrayList<double[]>();
		// forcast next 6 hours
		for (int i = 1; i < 7; i++) {
			DateTime nexthours = now.plusHours(i);
			//Next hour is Saturday
			if (nexthours.getDayOfWeek() == 6 || nexthours.getDayOfWeek() == 7) {

				((ARXFirstWEProvider) forecastWE).setARXForcastmodel(i);
				double[][] forcast_x = forecastWE.getARXmodel_X();
				double[] arrCopy = new double[26];
				
				if(now.getDayOfWeek() == 5){
					arrCopy = Utility.copyArray(forcast_x, i-(7-forcast_x.length), i-(7-forcast_x.length)+1);
				}else{
					arrCopy = Utility.copyArray(forcast_x, i-1, i);
				}
				
			

				result.add(arrCopy);
				//Next hour is regular working day
			} else if (nexthours.getDayOfWeek() >= 1
					&& nexthours.getDayOfWeek() <= 5) {

				((ARXFirstWDProvider) forecastWD).setARXForcastmodel(i);
				double[][] forcast_x = forecastWD.getARXmodel_X();
				double[] arrCopy = new double[26];
				
				if(now.getDayOfWeek() == 7){
					arrCopy = Utility.copyArray(forcast_x, i-(7-forcast_x.length), i-(7-forcast_x.length)+1);
				}else{
					arrCopy = Utility.copyArray(forcast_x, i-1, i);
				}
				
				
				result.add(arrCopy);
			}
		}
		
		return result;
	}
	
	
	/**
	 * Get next 7 to 24 hours based on current hour
	 * Use ARX(24,168) for the next 7 to 24 hours predication.
	 * @return 7 to 24 hours data
	 * @throws BiffException
	 * @throws ParseException
	 * @throws IOException
	 */
	public List<double[]> getNext7to24HoursData() throws BiffException, ParseException,IOException {
			DateTime now = new DateTime();
			IARXModel forecastWE = arxManager.createSecondWE();
			forecastWE.setForecastData();
			
			IARXModel forecastWD = arxManager.createSecondWD();
			forecastWD.setForecastData();
			
			List<double[]> result = new ArrayList<double[]>();
			// forecast next 7 to 24 hours
			for (int i = 7; i < 49; i++) {
				DateTime nexthours = now.plusHours(i);
				//Next hour is Saturday or Sunday
				if (nexthours.getDayOfWeek() == 6 || nexthours.getDayOfWeek() == 7) {
			
					((ARXSecondWEProvider) forecastWE).setARXForcastmodel();
					double[][] forcast_x = forecastWE.getARXmodel_X();
					double[] arrCopy = new double[17];
					
					if(now.getDayOfWeek() == 5 || (now.getDayOfWeek() == 4 && i >= 25)){
						arrCopy = Utility.copyArray(forcast_x, i-(49-forcast_x.length), i-(49-forcast_x.length)+1);
					}else{
						arrCopy = Utility.copyArray(forcast_x, i-1, i);
					}
				
			
					result.add(arrCopy);
					//Next hour is regular working day
				} else if (nexthours.getDayOfWeek() >= 1
						&& nexthours.getDayOfWeek() <= 5) {
			
					((ARXSecondWDProvider) forecastWD).setARXForcastmodel();
					double[][] forcast_x = forecastWD.getARXmodel_X();
					// copy the array according to the number of hour
					double[] arrCopy = new double[17];
					if(now.getDayOfWeek() == 7 || (now.getDayOfWeek() == 6 && i >= 25)){
						arrCopy = Utility.copyArray(forcast_x, i-(49-forcast_x.length), i-(49-forcast_x.length)+1);
					}else{
						arrCopy = Utility.copyArray(forcast_x, i-1, i);
					}
					
					result.add(arrCopy);
				}
			}
			
			return result;
	}
	
	

	/**
	 * Insert cofficient into database according to the latest data There are 7
	 * ARXModels ARX(1,2,24), ARX(2,3,24), ARX(3,4,24), ARX(4,5,24), ARX(5,6,24)
	 * and ARX(24,168)
	 * 
	 * @throws ParseException
	 * @throws BiffException
	 * @throws IOException
	 */
	public void insertCofficient(int hour) throws ParseException, BiffException,
			IOException {
		LocalDBDaoImpl l = new LocalDBDaoImpl();

		DateTime now = new DateTime();
		
		if (now.getDayOfWeek() >= 1 && now.getDayOfWeek() <= 5) {
			IARXModel arxFirstWD = new ARXFirstWDProvider();
			arxFirstWD.setHistdata();
			for (int i = 1; i <= 6; i++) {
				
				((ARXFirstWDProvider) arxFirstWD).setARXHistmodel(i, hour);

				Matrix cofficient = calculateCofficient(arxFirstWD, i);

				l.getEntityManager().getTransaction().begin();

				l.addRunData(i);

				for (int j = 0; j < cofficient.getArray().length; j++) {

					l.addResultData(i, j, cofficient.get(j, 0),hour);

				}

				l.getEntityManager().flush();
				l.getEntityManager().getTransaction().commit();

				System.out.println("ARXModel(" + i + "," + (i + 1)
						+ ", 24) b: "
						+ Arrays.deepToString(cofficient.getArray()));
			}
			
			IARXModel second = new ARXSecondWDProvider();

			second.setHistdata();

			((ARXSecondWDProvider) second).setARXHistmodel(hour);
		
			Matrix cofficient = calculateCofficient(second, 7);

			l.getEntityManager().getTransaction().begin();

			l.addRunData(7);

			for (int j = 0; j < cofficient.getArray().length; j++) {

				l.addResultData(7, j, cofficient.get(j, 0), hour);

			}

			l.getEntityManager().flush();
			l.getEntityManager().getTransaction().commit();

			System.out.println("ARXModel(24, 168) b: "
					+ Arrays.deepToString(cofficient.getArray()));
			l.getEntityManager().close();
		}else{
			IARXModel arxFirstWE = new ARXFirstWEProvider();
			arxFirstWE.setHistdata();
			for (int i = 1; i <= 6; i++) {
				
				((ARXFirstWEProvider) arxFirstWE).setARXHistmodel(i, hour);

				Matrix cofficient = calculateCofficient(arxFirstWE, i);

				l.getEntityManager().getTransaction().begin();

				l.addRunData(i);

				for (int j = 0; j < cofficient.getArray().length; j++) {

					l.addResultData(i, j, cofficient.get(j, 0),hour);

				}

				l.getEntityManager().flush();
				l.getEntityManager().getTransaction().commit();

				System.out.println("ARXModel(" + i + "," + (i + 1)
						+ ", 24) b: "
						+ Arrays.deepToString(cofficient.getArray()));
			}
			
			IARXModel arxSecondWE = new ARXSecondWEProvider();

			arxSecondWE.setHistdata();

			((ARXSecondWEProvider) arxSecondWE).setARXHistmodel(hour);
		
			Matrix cofficient = calculateCofficient(arxSecondWE, 7);

			l.getEntityManager().getTransaction().begin();

			l.addRunData(7);

			for (int j = 0; j < cofficient.getArray().length; j++) {

				l.addResultData(7, j, cofficient.get(j, 0), hour);

			}

			l.getEntityManager().flush();
			l.getEntityManager().getTransaction().commit();

			System.out.println("ARXModel(24, 168) b: "
					+ Arrays.deepToString(cofficient.getArray()));
			l.getEntityManager().close();
			
		}
	}
	
	public double[] getCofficient(int typeId, int hour, boolean weekday){
		LocalDBDaoImpl l = new LocalDBDaoImpl();
		List<Double> temp = l.getResult(typeId, hour, weekday);
		double[] result = new double[temp.size()];
		for(int i = 0 ; i < temp.size(); i++){
			result[i] = temp.get(i);
		}
		
		return result;
	}
	
	
	/**
	 * reset and insert a new group cofficient in database
	 * @throws BiffException
	 * @throws ParseException
	 * @throws IOException
	 */
	public void resetCofficient() throws BiffException, ParseException, IOException{
		
			for(int i = 0; i <=23; i++){
			this.insertCofficient(i);
		}
	}
	
	
	public void predictionCL() throws BiffException, ParseException, IOException{
		List<double[]> data_1to6 = this.getNext6HoursData();
		List<double[]> data_7to48 = this.getNext7to24HoursData();
		
		if(Utility.DEBUG){
			Utility.printArrayListDouble(data_1to6);
			Utility.printArrayListDouble(data_7to48);
		}
		
		DateTime date = new DateTime();
		int hour = date.getHourOfDay();
		
		LocalDBDaoImpl ldao = new LocalDBDaoImpl();
		this.insertCofficient(hour);
		ldao = new LocalDBDaoImpl();
		List<Double> rmse = ldao.getRMSE();
		Collections.reverse(rmse);
		
		//first six hour
		for(int i = 0; i < 6; i++){
			
			if((hour + 1 + i) <= 23){
				double[] array_b;
				if(date.plusHours(1+i).getDayOfWeek() >= 1 && date.plusHours(1+i).getDayOfWeek() <=5){
					 array_b = this.getCofficient(i+1, hour+1+i, true);
				}else{
					 array_b = this.getCofficient(i+1, hour+1+i, false);
				}
				Matrix X = new Matrix(data_1to6.get(i), data_1to6.get(i).length);
				Matrix b = new Matrix(array_b, array_b.length);
				Matrix Y = X.transpose().times(b);
				
				DateTime temp = date.plusHours(1+i);
				
				Timestamp targetHour = Utility.converToDateHH(temp.toDate());
				
				ldao.getEntityManager().getTransaction().begin();
				
				ldao.addPrediction(targetHour, (float)Y.get(0, 0), i+1, rmse.get(i));
				
				ldao.getEntityManager().flush();
				ldao.getEntityManager().getTransaction().commit();
				ldao.getEntityManager().close();
				System.out.printf("%d CL: %.2f \n",hour+i+1, Y.get(0, 0));
				//put the prediction value into the point after 24 hours
				if(!(date.plusHours(i+24).getDayOfWeek() == 6 && date.getDayOfWeek() == 4 || date.plusHours(i+24).getDayOfWeek() == 7 && date.getDayOfWeek() == 5 
						|| date.plusHours(i+24).getDayOfWeek() == 1 && date.getDayOfWeek() == 6 || date.plusHours(i+24).getDayOfWeek() == 2 && date.getDayOfWeek() == 7)){
					data_7to48.get(i+24-6)[7] = Y.get(0, 0);
				}
				
			}else{
				double[] array_b;
				if(date.plusHours(1+i).getDayOfWeek() >= 1 && date.plusHours(1+i).getDayOfWeek() <=5){
					array_b = this.getCofficient(i+1, hour+1+i-24, true);
				}
				else{
					array_b = this.getCofficient(i+1, hour+1+i-24, false);
				}
				Matrix X = new Matrix(data_1to6.get(i), data_1to6.get(i).length);
				Matrix b = new Matrix(array_b, array_b.length);
				Matrix Y = X.transpose().times(b);
				
				DateTime temp = date.plusHours(1+i);
				
				Timestamp targetHour = Utility.converToDateHH(temp.toDate());
				
				ldao.getEntityManager().getTransaction().begin();
				
				ldao.addPrediction(targetHour, (float)Y.get(0, 0), i+1, rmse.get(i));
				
				ldao.getEntityManager().flush();
				ldao.getEntityManager().getTransaction().commit();
				ldao.getEntityManager().close();
				System.out.printf("%d CL: %.2f \n",hour+i+1-24, Y.get(0, 0));
				if(!(date.plusHours(i+24).getDayOfWeek() == 6 && date.getDayOfWeek() == 4 || date.plusHours(i+24).getDayOfWeek() == 7 && date.getDayOfWeek() == 5 
						|| date.plusHours(i+24).getDayOfWeek() == 1 && date.getDayOfWeek() == 6 || date.plusHours(i+24).getDayOfWeek() == 2 && date.getDayOfWeek() == 7)){
					data_7to48.get(i+24-6)[7] = Y.get(0, 0);
				}
			}
			
		}
		
		//ARX(24,168)
		
		for(int i = 0; i < 42; i++){
			if( i < 18){
				if((hour + 7 + i) <= 23){
					
					double[] array_b;
					if(date.plusHours(7+i).getDayOfWeek() >= 1 && date.plusHours(7+i).getDayOfWeek() <=5){
						 array_b = this.getCofficient(7, hour+7+i, true);
					}
					else{
						 array_b = this.getCofficient(7, hour+7+i, false);
					}
					Matrix X = new Matrix(data_7to48.get(i), data_7to48.get(i).length);
					Matrix b = new Matrix(array_b, array_b.length);
					Matrix Y = X.transpose().times(b);
					
					
					DateTime temp = date.plusHours(7+i);
					Timestamp targetHour = Utility.converToDateHH(temp.toDate());
					
					ldao.getEntityManager().getTransaction().begin();
					
					ldao.addPrediction(targetHour, (float)Y.get(0, 0), 7, rmse.get(6));
					
					ldao.getEntityManager().flush();
					ldao.getEntityManager().getTransaction().commit();
					ldao.getEntityManager().close();
					System.out.printf("%d CL: %.2f \n",hour+i+7, Y.get(0, 0));
					if(!(date.plusHours(i+7+24).getDayOfWeek() == 6 && date.getDayOfWeek() == 4 || date.plusHours(i+7+24).getDayOfWeek() == 7 && date.getDayOfWeek() == 5 
							|| date.plusHours(i+7+24).getDayOfWeek() == 1 && date.getDayOfWeek() == 6 || date.plusHours(i+7+24).getDayOfWeek() == 2 && date.getDayOfWeek() == 7)){
						data_7to48.get(i+24)[7] = Y.get(0, 0);
					}
					
				}else if((hour + 7 + i) <= 47){
					double[] array_b;
					if(date.plusHours(7+i).getDayOfWeek() >= 1 && date.plusHours(7+i).getDayOfWeek() <=5){
						array_b = this.getCofficient(7, hour+7+i-24, true);
					}
					else{
						array_b = this.getCofficient(7, hour+7+i-24, false);
					}
					
					Matrix X = new Matrix(data_7to48.get(i), data_7to48.get(i).length);
					Matrix b = new Matrix(array_b, array_b.length);
					Matrix Y = X.transpose().times(b);
					
					
					DateTime temp = date.plusHours(7+i);
					Timestamp targetHour = Utility.converToDateHH(temp.toDate());
					
					ldao.getEntityManager().getTransaction().begin();
					
					ldao.addPrediction(targetHour, (float)Y.get(0, 0), 7, rmse.get(6));
					
					ldao.getEntityManager().flush();
					ldao.getEntityManager().getTransaction().commit();
					ldao.getEntityManager().close();
					System.out.printf("%d CL: %.2f \n",hour+i+7-24, Y.get(0, 0));
					if(!(date.plusHours(i+7+24).getDayOfWeek() == 6 && date.getDayOfWeek() == 4 || date.plusHours(i+7+24).getDayOfWeek() == 7 && date.getDayOfWeek() == 5 
							|| date.plusHours(i+7+24).getDayOfWeek() == 1 && date.getDayOfWeek() == 6 || date.plusHours(i+7+24).getDayOfWeek() == 2 && date.getDayOfWeek() == 7)){
						data_7to48.get(i+24)[7] = Y.get(0, 0);
					}
				}
			
			}else{
				if((hour + 7 + i) <= 47){
					double[] array_b;
					if(date.plusHours(7+i).getDayOfWeek() >= 1 && date.plusHours(7+i).getDayOfWeek() <=5){
						array_b = this.getCofficient(7, hour+7+i-24, true);
					}
					else{
						array_b = this.getCofficient(7, hour+7+i-24, false);
					}
					
					Matrix X = new Matrix(data_7to48.get(i), data_7to48.get(i).length);
					Matrix b = new Matrix(array_b, array_b.length);
					Matrix Y = X.transpose().times(b);
					
					
					DateTime temp = date.plusHours(7+i);
					Timestamp targetHour = Utility.converToDateHH(temp.toDate());
					
					ldao.getEntityManager().getTransaction().begin();
					
					ldao.addPrediction(targetHour, (float)Y.get(0, 0), 0, -1);
					
					ldao.getEntityManager().flush();
					ldao.getEntityManager().getTransaction().commit();
					ldao.getEntityManager().close();
					System.out.printf("%d CL: %.2f \n",hour+i+7-24, Y.get(0, 0));
					
				}
				else{
					
					double[] array_b;
					if(date.plusHours(7+i).getDayOfWeek() >= 1 && date.plusHours(7+i).getDayOfWeek() <=5){
						array_b = this.getCofficient(7, hour+7+i-48, true);
					}
					else{
						array_b = this.getCofficient(7, hour+7+i-48, false);
					}
					
					Matrix X = new Matrix(data_7to48.get(i), data_7to48.get(i).length);
					Matrix b = new Matrix(array_b, array_b.length);
					Matrix Y = X.transpose().times(b);
					
					
					DateTime temp = date.plusHours(7+i);
					Timestamp targetHour = Utility.converToDateHH(temp.toDate());
					
					ldao.getEntityManager().getTransaction().begin();
					
					ldao.addPrediction(targetHour, (float)Y.get(0, 0), 0, -1);
					
					ldao.getEntityManager().flush();
					ldao.getEntityManager().getTransaction().commit();
					ldao.getEntityManager().close();
					System.out.printf("%d CL: %.2f \n",hour+i+7-48, Y.get(0, 0));
					
				}
				
			}
		}
		
//		Utility.printArrayListDouble(data_1to6);
//		Utility.printArrayListDouble(data_7to48);
	}
	
/**
 * Insert the missing data into local database.
 * @throws ParseException
 */
	public void insertMissingData() throws ParseException{
		LocalDBDaoImpl l = new LocalDBDaoImpl();

		
		List<Map<Timestamp, Float>> TandRHData = l.getHistMissData();
		Map<Timestamp, Float> T_t = TandRHData.get(0);
		Map<Timestamp, Float> RH_t = TandRHData.get(1);
		
		l.getEntityManager().getTransaction().begin();
		for(Timestamp date : T_t.keySet()){
			l.addMissData(date, T_t, RH_t);
		}
		
		l.getEntityManager().flush();
		l.getEntityManager().getTransaction().commit();
		l.getEntityManager().close();
	}
	/**
	 * @param args
	 * @throws IOException
	 * @throws ParseException
	 * @throws BiffException
	 */
	public static void main(String[] args) throws IOException, BiffException,ParseException {

		Forecasting forecasting = new Forecasting();
		forecasting.insertMissingData();
		
		boolean reset = false;
		
		if(reset){
			forecasting.resetCofficient();
		}else{
			forecasting.predictionCL();
		}
		
	}

}
